import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;

public class ChatServer implements Observer, Runnable {
	private static final int PORT = 5555;
	private Map<String, UserSenderConnection> senders;
	private ServerSocket servSocket;

	public ChatServer() {
		senders = new HashMap<String, UserSenderConnection>();
		try {
			servSocket = new ServerSocket(PORT);
			System.out.println("Server avviato");
		} catch (IOException e) {
			System.out
					.println("Impossibile avviare il server. Probabilemente la porta è occupata o il server è già attivo.");
		}
	}

	public void run() {
		if (servSocket != null) {
			while (true) {
				Socket sock;
				try {
					sock = servSocket.accept();

					UserReceiverConnection receiver = new UserReceiverConnection(
							sock);
					receiver.addObserver(this);
					Thread receiverThread = new Thread(receiver);
					receiverThread.start();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	public static void main(String args[]) {
		new Thread(new ChatServer()).start();
	}

	/**
	 * Gestisce gli eventi di ricezione di un nuovo messaggio da parte di un
	 * client.
	 * 
	 * @param arg
	 *            oggetto di tipo Message rappresentante il messaggio
	 * @param o
	 *            UserReceiverConnection che ha ricevuto il messaggio
	 */
	@Override
	public void update(Observable o, Object arg) {
		UserReceiverConnection receiver = (UserReceiverConnection) o;
		// In caso di errore dealloco le risorse per quella connessione
		if (arg == null) {
			closeConnection(receiver);
			System.out.println("Utente " + receiver.getNickname()
					+ " disconnesso");
			// Avviso i client connessi alla chat
			notifyDisconnection(receiver.getNickname());
		} else {
			Message m = (Message) arg;
			switch (m.getOperation()) {
			case CLOSE:
				// Chiudo la connessione associata al client
				closeConnection(receiver);
				System.out
						.println("Utente " + m.getNickName() + " disconnesso");
				// Avviso i client connessi alla chat
				notifyDisconnection(m.getNickName());

				break;
			case MESSAGE:
				// Inoltro il messaggio a tutti i client
				sendMessage(m);

				break;
			case OPEN:
				// Se è il primo messaggio dell'utente avvio il thread che
				// gestisce la comunicazione con l'utente stesso
				if (!senders.containsKey(m.getNickName())) {
					addSender(receiver.getSocket(), m.getNickName());
					receiver.setNickname(m.getNickName());

					System.out.println("Utente " + m.getNickName()
							+ " connesso");
					
					// Avviso i client connessi alla chat
					notifyConnection(m.getNickName());
				}
				break;
			case INFORMATION:
				// Utilizzato solo dal server verso i client
				break;
			case CLIENTLIST:
				break;
			default:
				break;

			}

		}

	}

	/**
	 * Consente di chiudere la connessione con un client
	 * 
	 * @param receiver
	 *            UserReceiverConnection che è associato al client
	 */
	private void closeConnection(UserReceiverConnection receiver) {
		if (senders.containsKey(receiver.getNickname())) {
			UserSenderConnection s = senders.remove(receiver.getNickname());
			s.setStopped();
			// Consente di interrompere l'attesa sulla coda di messaggi e
			// chiudere il thread
			s.addMessageToSend(null);
			try {
				s.getSocket().close();
			} catch (IOException e) {
			}
		}
		receiver.setStopped();
	}

	private void notifyConnection(String nickname) {
		sendMessage(new Message(nickname, "L'utente " + nickname
				+ " si è unito alla chat", OperationType.INFORMATION));
		sendUpdateClientListMessage();
	}

	private void notifyDisconnection(String nickname) {
		sendMessage(new Message(nickname, "L'utente " + nickname
				+ " ha abbandonato la chat", OperationType.INFORMATION));
		sendUpdateClientListMessage();
	}

	private void sendMessage(Message m) {
		for (Map.Entry<String, UserSenderConnection> entry : senders.entrySet()) {
			if (entry.getKey() != m.getNickName())
				entry.getValue().addMessageToSend(m);
		}
	}
	
	private String getConnectedClients()
	{
		String s = "";
		for (Map.Entry<String, UserSenderConnection> entry : senders.entrySet()) {
			s+=entry.getKey()+"\n";
		}
		return s;
		
	}
	
	private void sendUpdateClientListMessage(){
		String s =getConnectedClients();
		sendMessage(new Message("",s,OperationType.CLIENTLIST));
	}

	private void addSender(Socket s, String nickname) {
		UserSenderConnection senderThread;
		try {
			senderThread = new UserSenderConnection(s);
			senderThread.start();
			senders.put(nickname, senderThread);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
